package sf.common.log;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sf.database.OrmConfig;
import sf.database.template.sql.SqlHelp;
import sf.tools.ArrayUtils;
import sf.tools.StringUtils;
import sf.tools.SystemUtils;

import java.lang.reflect.Array;
import java.sql.Connection;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.function.Supplier;

public class OrmLog {
    private static Logger logger = LoggerFactory.getLogger(OrmLog.class);

    public static int LENGTH = Integer.MAX_VALUE;//256;

    public static String lineSeparator = SystemUtils.lineSeparator;

    /**
     * 支持 List,Set,数组等参数
     * @param context
     * @param sql
     * @return
     */
    public static LogContext commonLog(LogContext context, String sql) {
        return commonLog(context, true, sql, null);
    }

    /**
     * 日志对象初始化,支持 List,Set,数组等参数
     * @param context 可为null
     * @param sql
     * @param paras
     * @return
     */
    public static LogContext commonLog(LogContext context, String sql, Object paras) {
        return commonLog(context, true, sql, paras);
    }

    /**
     * 日志对象初始化,支持 List,Set,数组等参数
     * @param context 可为null
     * @param sql
     * @param format
     * @param paras
     * @return
     */
    public static LogContext commonLog(LogContext context, boolean format, String sql, Object paras) {
        if (sql != null && needShowSql()) {
            if (context == null) {
                context = new LogContext();
            }
            if (context.getStart() == null) {
                context.setStart(System.currentTimeMillis());
            }
            //是否开启日志打印
            StringBuilder sb = new StringBuilder();
            if (format && (sql.contains("\r") || sql.contains("\n") || sql.contains("\t"))) {
                sql = SqlHelp.replaceLineSql(sql);
            }
            sb.append("SQL: ").append(sql);
            appendLog(sb, paras);
//          context.setSql(sb.toString());
            context.setPrintSql(sb);
            context.setParas(paras);
            return context;
        } else {
            return null;
        }
    }


    /**
     * 批量日志对象初始化,支持 List,Set,数组等参数
     * @param context   可为null
     * @param sql
     * @param batch     是否是批量操作
     * @param batchSize 每批次总数
     * @param current   当前批次总数
     * @param paras
     * @return
     */
    public static LogContext batchCommonLog(Logger logger, LogContext context, String sql, boolean batch, int batchSize, int current, Object paras) {
        return batchCommonLog(logger, context, sql, true, batch, batchSize, current, paras);
    }

    /**
     * 批量日志对象初始化,支持 List,Set,数组等参数
     * @param context   可为null
     * @param sql
     * @param format
     * @param batch     是否是批量操作
     * @param batchSize
     * @param current
     * @param paras
     * @return
     */
    public static LogContext batchCommonLog(Logger logger, LogContext context, String sql, boolean format, boolean batch, int batchSize, int current, Object paras) {
        if (!batch) {
            return commonLog(context, format, sql, paras);
        }
        if (sql != null && needShowSql()) {
            if (context == null) {//此处处理主要是为了解决批量sql语句的大批量打印.
                context = new LogContext();
            }
            if (context.getExecuteKey() == null) {
                context.setExecuteKey(StringUtils.uuid32());
            }
            if (context.getStart() == null) {
                context.setStart(System.currentTimeMillis());
            }
            StringBuilder sb = context.getPrintSql();
            if (sb == null) {
                sb = new StringBuilder();
            }
            if (sb.length() > 0) {
                sb.append(lineSeparator);
            }
            if (format && (sql.contains("\r") || sql.contains("\n") || sql.contains("\t"))) {
                sql = SqlHelp.replaceLineSql(sql);
            }
            sb.append("Batch(").append(context.getExecuteKey()).append("):").append(current).append("/").append(batchSize).append(" ").append(sql);
            appendLog(sb, paras);
            println(logger, sb);
//            context.setSql(sb.toString());
//            context.setPrintSql(sb);
//            context.setParas(paras);
            return context;
        } else {
            return null;
        }
    }

    private static void appendLog(StringBuilder sb, Object paras) {
        if (paras != null) {
            if (paras instanceof Collection) {
                for (Object p : (Collection<?>) paras) {
                    builderLog(sb, p);
                }
            } else if (paras.getClass().isArray()) {
                int length = Array.getLength(paras);
                for (int i = 0; i < length; i++) {
                    Object value = Array.get(paras, i);
                    builderLog(sb, value);
                }
            } else {
                builderLog(sb, paras);
            }
        }
    }

    private static void builderLog(StringBuilder sb, Object p) {
        String value = String.valueOf(p);
        if (value.length() >= LENGTH) {
            value = value.substring(0, LENGTH) + "...(" + value.length() + ")";
        }
        sb.append(lineSeparator).append("Param => ").append(value);
    }

    public static void sqlLog(Logger logger, LogContext context, Supplier<Boolean> autoCommitSupplier) {
        if (context != null && needShowSql()) {
            StringBuilder sb = context.getPrintSql();
            if (sb == null) {
                sb = new StringBuilder();
                if (context.getSql() != null) {
                    sb.append(context.getSql());
                }
            }
            if (context.getExecuteKey() == null) {
                context.setExecuteKey(StringUtils.uuid32());
            }
            if (sb.length() > 0) {
                sb.append(lineSeparator);
            }
            if (OrmConfig.getInstance().isUseSystemPrint()) {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
                sb.append("Execute time: ").append(sdf.format(new Date())).append(' ');
            }
            if (context.getExecuteKey() != null) {
                sb.append("Execute Key:").append(context.getExecuteKey()).append(' ');
            }
            if (autoCommitSupplier != null) {
                sb.append("AutoCommit: ").append(autoCommitSupplier.get());
            }
            println(logger, sb);
        }
    }

    public static void resultLog(Logger logger, LogContext context, Object result) {
        if ((context != null || result != null) && needShowSql()) {
            StringBuilder sb = new StringBuilder();
            if (context != null) {
                if (context.getStart() != null) {
                    sb.append("Time cost: ").append((System.currentTimeMillis() - context.getStart())).append("ms");
                }
            }
            if (OrmConfig.getInstance().isUseSystemPrint()) {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
                sb.append(' ').append("Execute time: ").append(sdf.format(new Date()));
            }
            if (result != null) {
                if (result instanceof Collection) {
                    sb.append(lineSeparator).append("Result:  [").append(((Collection<?>) result).size()).append("]");
                } else if (result.getClass().isArray()) {
                    String r = StringUtils.join(ArrayUtils.objectToArray(result), ",");
                    sb.append(lineSeparator).append("Result:  [").append(r).append("]");
                } else {
                    sb.append(lineSeparator).append("Result:  [").append(result).append("]");
                }
            }
            //是否需要打印方法调用链
            if (OrmConfig.getInstance().isShowCallChain()) {
                RuntimeException e = new RuntimeException();
                StackTraceElement[] traces = e.getStackTrace();
                List<StackTraceElement> list = lookBusinessCodeInTrace(traces);
                sb.append(lineSeparator).append("CallChain: ");
                sb.append(list.toString());
            }
            println(logger, sb);
        }
    }

    public static void resultOnly(Logger logger, LogContext context, Object result) {
        if (result != null && needShowSql()) {
            StringBuilder sb = new StringBuilder();
            if (context.getExecuteKey() != null) {
                sb.append("Execute Key:").append(context.getExecuteKey()).append(' ');
            }
            if (result instanceof Collection) {
                sb.append(lineSeparator).append("Result:  [").append(((Collection<?>) result).size()).append("]");
            } else if (result.getClass().isArray()) {
                String r = StringUtils.join(ArrayUtils.objectToArray(result), ",");
                sb.append(lineSeparator).append("Result:  [").append(r).append("]");
            } else {
                sb.append(lineSeparator).append("Result:  [").append(result).append("]");
            }
            println(logger, sb);
        }
    }

    public static void resultTime(Logger logger, LogContext context) {
        if (context != null && needShowSql()) {
            StringBuilder sb = new StringBuilder();
            if (context.getExecuteKey() != null) {
                sb.append("Execute Key:").append(context.getExecuteKey()).append(' ');
            }
            if (context.getStart() != null) {
                sb.append("Time cost: ").append((System.currentTimeMillis() - context.getStart())).append("ms");
            }
            println(logger, sb);
        }
    }

    /**
     * 日志打印
     * @param context
     */
    public static void resultSqlLog(Logger logger, LogContext context, Throwable ex) {
        resultSqlLog(logger, context, null, null, ex);
    }

    /**
     * 日志打印
     * @param context
     * @param result
     * @param autoCommitSupplier
     */
    public static void resultSqlLog(Logger logger, LogContext context, Object result, Supplier<Boolean> autoCommitSupplier) {
        resultSqlLog(logger, context, result, autoCommitSupplier, null);
    }

    public static void resultSqlLog(Logger logger, LogContext context, Object result, Supplier<Boolean> autoCommitSupplier, Throwable ex) {
        resultSqlLog(logger, context, result, autoCommitSupplier, ex, false);
    }

    public static void resultSqlLog(Logger logger, LogContext context, Object result, Supplier<Boolean> autoCommitSupplier, Throwable ex, boolean printForce) {
        if (context != null && needShowSql()) {
            StringBuilder sb = context.getPrintSql();
            if (sb == null) {
                sb = new StringBuilder();
                if (context.getSql() != null && ex != null) {
                    sb.append(context.getSql());
                }
            }
            if (context.getExecuteKey() != null) {
                sb.append("Execute Key:").append(context.getExecuteKey());
            }
            if (sb.length() > 0) {
                sb.append(lineSeparator);
            }
            if (context.getStart() != null) {
                sb.append("Time cost: ").append((System.currentTimeMillis() - context.getStart())).append("ms");
            }
            if (OrmConfig.getInstance().isUseSystemPrint()) {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
                sb.append(" ").append("Execute time: ").append(sdf.format(new Date()));
            }
            if (autoCommitSupplier != null) {
                sb.append(", AutoCommit: ").append(autoCommitSupplier.get());
            }
            if (result != null) {
                sb.append(lineSeparator).append("Result:  ");
                if (result instanceof Collection) {
                    if (printForce) {
                        sb.append(result);
                    } else {
                        sb.append("[").append(((Collection<?>) result).size()).append("]");
                    }
                } else if (result.getClass().isArray()) {
                    String r = StringUtils.join(ArrayUtils.objectToArray(result), ",");
                    sb.append("[").append(r).append("]");
                } else {
                    sb.append("[").append(result).append("]");
                }
            }

            //是否需要打印方法调用链
            if (OrmConfig.getInstance().isShowCallChain()) {
                RuntimeException e = new RuntimeException();
                StackTraceElement[] traces = e.getStackTrace();
                List<StackTraceElement> list = lookBusinessCodeInTrace(traces);
                sb.append(lineSeparator).append("CallChain: ");
                sb.append(list.toString());
            }
            println(logger, sb);
        }
    }

    protected static void println(Logger log, StringBuilder sb) {
        //是否使用系统打印日志
        if (OrmConfig.getInstance().isUseSystemPrint()) {
            sb.append(lineSeparator);
            System.out.println(sb);
        } else {
            String str = sb.toString();
            if (!str.startsWith(lineSeparator)) {
                str = lineSeparator + str;
            }
            if (log == null) {
                log = logger;
            }
            if (log.isInfoEnabled()) {
                log.info(str);
            }
        }
    }

    protected static List<StackTraceElement> lookBusinessCodeInTrace(StackTraceElement[] traces) {
        List<StackTraceElement> list = new ArrayList<>();
        for (int i = 1; i < traces.length - 1; i++) {
            String name = traces[i].getClassName();
            list.add(traces[i]);
            if (!name.startsWith("sf") && !name.contains("$") && !name.startsWith("java")) {
                break;
            }
        }
        return list;
    }

    public static Boolean getAutoCommit(Connection connection) {
        if (connection == null) {
            return null;
        }
        try {
            return connection.getAutoCommit();
        } catch (SQLException ignored) {

        }
        return null;
    }

    public static boolean needShowSql() {
        return OrmConfig.getInstance().isShowSql();
    }
}
